<?php
/**
 * 数据表模型类
 *
 * @copyright (c) 2006 - 2007 与非网 (www.eefocus.com)
 * @author	kokko<kokko313@gmail.com>
 * @package com
 * @subpackage eefocus
 * @version 1.0 2006-11-7
 */
class Model{
	/**
	 * 数据库链接对象
	 * 
	 * @var object 
	 */
	public static $_conn = null;
	
	/**
	 * 设置是否自动提交
	 *
	 * @var bool
	 * @access public
	 */
	public  $_autoCommit = true;

	/**
	 * 是否自动清除查询条件
	 * 通常我们在每次查询之后就会自动清楚上次查询的条件语句
	 *
	 * @var array
	 * @access public
	 */
	public  $_autoClear = true;

	/**
	 * 查询条件
	 *
	 * @var bool
	 * @access public
	 */
	public $_condition = array();
	
	/**
	 * 数据表名字 （不包含表前缀）
	 * 
	 * @var string
	 * @access protected
	 */
	protected $_tableName = '';

	/**
	 * 数据表前缀
	 * 
	 * @var string
	 * @access protected
	 */
	protected $_tablePrefix = '';

	/**
	 * 数据表后缀
	 * 
	 * @var string
	 * @access protected
	 */
	protected $_tableSuffix = '';

	/**
	 * 实际数据表名（包含表前后缀）
	 * 
	 * @var string
	 * @access protected
	 */ 
	protected $_trueTableName ='';
	
	/**
	 * 数据库表主键
	 *
	 * @var mix 多主键的时候用逗号分开，id1,id2
	 * @access protected
	 */
	protected $_tablePK = '';
	
	/**
	 * 数据库表字段信息
	 * 包括字段名，字段类型，是否为空，是否有默认值
	 * 
	 * @access protected
	 */
	protected $_fields = null;
	
	/**
	 * 字段值集合
	 *
	 * @var array
	 * @access protected
	 */
	protected $_fieldsData = array();

	/**
	 * 构造函数
	 *
	 */
	function __construct(){
		// 获取数据库操作对象
		if( self::$_conn == null ){
			self::$_conn = Db::getInstance();
		}
		//获取真实表名
		$this->_trueTableName = $this->_tablePrefix.$this->_tableName.$this->_tableSuffix;

		// 数据表字段检测
		$this->getTableInfo();
	}
	
	/**
	 * 插入数据
	 * 
	 * @access public
	 * @param $data
	 * @return bool or int 返回的是插入的id或者affectedRows
	 */
	function create( $data ){
		$data = $this->facade( $data );
		$table = $this->_trueTableName;
		if(!empty($data)){
			if( !empty( $this->_condition) ){
				$table = !empty( $this->_condition['table'] ) ? $this->_condition['table'] : $table;
				if( $this->_autoClear )
					$this->clearCondition();
			}
			if( self::$_conn->insert( $table,$data ) ){
				$insertId = self::$_conn->insertId();
				return $insertId>0 ? $insertId:self::$_conn->affectedRows();
			}
		}
		return false;
	}
	
	/**
	 * 插入多组数据
	 *
	 * @access public
	 * @param $data
	 * @return bool or int 返回affectedRows
	 */
	function createAll( $data ){
		array_walk( $data,array( $this,'facade' ) );
		$table = $this->_trueTableName;
		if( !empty( $data ) ){
			if( !empty( $this->_condition) ){
				$table = !empty( $this->_condition['table'] ) ? $this->_condition['table'] : $table;
				if( $this->_autoClear )
					$this->clearCondition();
			}
			if( self::$_conn->insertAll( $table,$data ) ){
				return self::$_conn->affectedRows();
			}
		}
		return false;
	}

	/**
	 * 根据where条件查询数据
	 * 
	 * @access public
	 * @param string $where where条件语句
	 * @param string $fields 获取field
	 * @param string $limit
	 * @param string $order
	 * @param string $join
	 * @param string $group
	 * @param bool	$cache	是否缓存
	 * @return array
	 */
	function find( $where=null,$fields='*',$limit='',$order='',$join='',$group='',$cache=false ){
		return $this->read( $cache,$where,$fields,$limit,$order,$join,$group );
	}
	
	/**
	 * 根据主键查询数据
	 * 多主键支持 findByPk( '1,2' ),findByPk( array( 'id'=>1,'tid'=>2 ) ),
	 *
	 * @access public
	 * @param mix $pk 主键
	 * @param string $fields 获取field
	 * @param bool	$cache	是否缓存 
	 * @return array
	 */
	function findByPk( $pk,$fields='*',$cache = false ){
		$where = $this->getPkCondition( $pk );
		return $this->read( $cache,$where,$fields,1 );
	}
	
	/**
	 * 根据字段值查询数据
	 * findByFields( 'id=1,tid=2,cid=3' )
	 * findByFields( 'id=1 AND tid=2 AND cid=3' )
	 * findByFields( array( 'id'=>1,'tid'=>2 ) )
	 * 注意，当Field值中含有逗号时请使用数组方式
	 *
	 * @access public
	 * @param mix $fields 字段值
	 * @param string $fields 获取field
	 * @param bool	$cache	是否缓存 
	 */
	function findByFields( $where,$fields = '*',$limit='',$order='',$join='',$group='',$cache = false ){
		if( is_string( $where ) ){
			$where = str_replace( ',',' AND ',$fields );
		}else{
			$where = $where;
		}
		return $this->read( $cache,$where,$fields,$limit,$order,$join,$group );
	}

	/**
     * 查询符合条件的第N条记录
     * 0 表示第一条记录,1表示第2行记录，-1代表最后一行
	 *
	 * @access public 
     * @param integer $position 记录位置
     * @param mixed $where 条件
     * @param string $order 排序
     * @param string $fields 字段名，默认为*
     * @return mixed
     */
	function findN( $position=0,$where=null,$order='',$fields='*' ){
		$limit = $position.',1';
		if( $position<0 ){
			$this->_autoClear = false; //关闭自动条件清除
			$total = $this->count( $where,$fields );
			$limit = ($total+$position).',1';
			$this->_autoClear = true;	//开启自动条件清除
		}
		$rs = $this->read( false,$where,$fields,$limit );
		return $rs[0];
	}
	/**
	 * 查找前N个记录
	 * 
	 * @access public
	 * @param int $count
	 * @param string $where where条件语句
	 * @param string $fields 获取field
	 * @param string $order
	 * @param string $join
	 * @param string $group
	 * @param bool	$cache	是否缓存
	 * @return array
	 */
	public function top($count,$where='',$fields='*',$order='',$join='',$group='',$cache=false) {
		$limit = '0,'.$count;
		return $this->find( $where,$fields,$limit,$order,$join,$group,$cache );
	}

	/**
     * 更新数据
	 *
     * @access public 
     * @param mixed $data 要更新的数据
     * @param mixed $where 更新数据的条件
     * @param integer $limit 要更新的记录数
     * @param string $order  更新的顺序
     * @return boolen or int
     */
	function update( $data,$where=null,$limit = null,$order=null ){
		if( isset( $data[$this->_tablePK] ) ){
			return $this->updateByPk( $data[$this->_tablePK],$data );
		}
		return $this->doUpdate( $data,$where,$limit,$order );
	}
	
	/**
     * 根据主键更新记录
	 *
     * @access public 
	 * @param mixed $pk 主键
     * @param mixed $data 要更新的数据
     * @return boolen or int
     */
	function updateByPk( $pk,$data ){
		$where = $this->getPkCondition( $pk );
		return $this->doUpdate( $data,$where,1,null );
	}

	/**
	 * 根据字段值查询数据
	 * updataByFields( 'id=1,tid=2,cid=3',$data )
	 * updataByFields( array( 'id'=>1,tid=>2 ),$data )
	 * 注意，当Field值中含有逗号时请使用数组方式
	 *
	 * @access public
	 * @param mix $fields 字段值
	 * @param mixed $data 要更新的数据
	 * @return boolen or int
	 */
	function updateByFields( $fields,$data ){
		if( is_string( $fields ) ){
			$where = str_replace( ',',' AND ',$fields );
		}else{
			$where = $fields;
		}
		return $this->doUpdate( $data,$where );
	}
	
	/**
	 * 删除数据
	 *
	 * @access public 
	 * @param string $where where条件语句
	 * @param integer $limit 要删除的记录数
     * @return boolen or int
	 */
	function remove( $where,$limit=null ){
		return $this->delete( $where,$limit );
	}
	
	/**
	 * 根据主键删除记录
	 *
	 * @access public 
	 * @param mixed $pk 主键
     * @return boolen or int
	 */
	function removeByPk( $pk ){
		$where = $this->getPkCondition( $pk );
		return $this->remove( $where,1 );
	}
	
	/**
	 * 根据指定的字段删除记录
	 * deleteByFields( 'id=1,tid=2,cid=3' )
	 * deleteByFields( array( 'id'=>1,tid=>2 ) )
	 * 注意，当Field值中含有逗号时请使用数组方式
	 *
	 * @access public 
	 * @param mixed $pk 字段值
     * @return boolen or int
	 */
	function removeByFields( $fields ){
		if( is_string( $fields ) ){
			$where = str_replace( ',',' AND ',$fields );
		}else{
			$where = $fields;
		}
		return $this->remove( $where );
	}
	
	/**
	 * 替换数据
	 *
	 * @access public 
	 * @param mixed $data 要更新的数据
     * @return boolen or int
	 */
	function replace( $data ){
		if( isset( $data[$this->_tablePK] ) ){
			$pk = $this->getPkCondition( $data[$this->_tablePK] );
			$data = array_merge( $data,$pk );
		}
		$data = $this->facade( $data );
		$table = $this->_trueTableName;
		if( !empty( $this->_condition) ){
			$table = !empty( $this->_condition['table'] ) ? $this->_condition['table'] : $table;
			if( $this->_autoClear )
				$this->clearCondition();
		}
		if ( self::$_conn->replace( $table,$data ) )
			return self::$_conn->affectedRows();
		return false;
	}

	/**
     * 取得某个字段的最大值
	 *
     * @access public 
     * @param string $field  字段名
     * @param mixed $where  条件
     * @return integer
     */
	public function max( $field,$where = null )
	{
		$fields = 'MAX('.$field.') as max';
		$rs = $this->read( false,$where,$fields,1 );
		return $rs['max']|0;
	}

	/**
     * 取得某个字段的最小值
	 *
     * @access public 
     * @param string $field  字段名
     * @param mixed $where  条件
     * @return integer
     */
	public function min( $field,$where = null )
	{
		$fields = 'MIN('.$field.') as min';
		$rs = $this->read( false,$where,$fields,1 );
		return $rs['min']|0;
	}

	/**
     * 统计某个字段的总和
	 *
     * @access public 
     * @param string $field  字段名
     * @param mixed $where  条件
     * @return integer
     */
	public function sum( $field,$where = null )
	{
		$fields = 'SUM('.$field.') as sum';
		$rs = $this->read( false,$where,$fields,1 );
		return $rs['sum']|0;
	}

	/**
     * 统计满足条件的记录个数
	 *
     * @access public 
     * @param mixed $where  条件
	 * @param string $field  字段名
     * @return integer
     */
	public function count($where='',$field='*')
	{
		$fields = 'COUNT('.$field.') as count';
		$rs = $this->read( false,$where,$fields,1 );
		return $rs['count']|0;
	}

	/**
     * 统计某个字段的平均值
	 *
     * @access public 
     * @param string $field  字段名
     * @param mixed $condition  条件
     * @return integer
     */
	public function avg($field,$condition='')
	{
		$fields = 'AVG('.$field.') as avg';
		$rs = $this->read( false,$condition,$fields,1 );
		return $rs['avg']|0;
	}

	/**
     * 查询SQL组装 table
	 *
     * @access public 
     * @param mixed $table 
     * @return Model
     */
	function table( $table ){
		$this->_condition['table']	=	$table;
		return $this;
	}
	
	/**
     * 查询SQL组装 where
	 * $conditon 为空 $fields为数字，为主键查询
	 * $conditon 为空 $fields为数组，implode And
	 * $conditon 为空 $fields为字符串，直接赋值
	 * $condition为数组 $fields为字符串，使用IN连接
     * $condition为字符串 $fields为字符串，$fields.'='.$condition
	 *
     * @access public 
     * @param mixed $fields
	 * @param mixed $condition
     * @return Model
     */
	function where( $fields,$condition = null ){
		if( empty( $condition ) ){
			//是个数字
			if( is_numeric( $fields ) ){
				$this->_condition['where'] = $this->_tablePK.'='.$fields;
			}else{	//是个字符串 是个数组
				$this->_condition['where'] = $fields;
			}
		}else{
			$this->_condition['where'][$fields] = $condition;
		}
		return $this;
	}
	
	/**
     * 查询SQL组装 field
	 *
     * @access public 
     * @param string $field 
     * @return Model
     */
	function field( $field ){
		$this->_condition['field'] = $field;
		return $this;
	}
	
	/**
     * 查询SQL组装 limit
	 *
     * @access public 
     * @param array $limit 
     * @return Model
     */
	function limit( $limit ){
		$this->_condition['limit'] = $limit;
		return $this;
	}

	/**
     * 查询SQL组装 order
	 *
	 * @access public 
     * @param mix $order string or array
     * @return Model
     */
	function order( $order ){
		if( is_array( $order ) )
			$order = implode( ',',$order );
		$this->_condition['order'] = $order;
		return $this;
	}
	
	/**
     * 查询SQL组装Left join
	 * join( array( 'user'=>'id=uid','blog'=>'id=blogid' ) )
	 * join( 'user ON user.id=blog.id' )
	 *
     * @access public 
	 * @param array $join 
     * @return Model
     */
	function join( $join ){
		$trueTable = isset( $this->_condition['table'] ) ? $this->_condition['table'] : $this->_trueTableName;
		if( is_array( $join ) ){
			$joins = array();
			foreach( $join as $table=> $_join ){
				$joins[] = str_replace( '=','='.$trueTable.'.',$table.'.'.$_join );
			}
			$join = implode( ' LEFT JOIN ',$joins );
		}
		$this->_condition['join'] = $join;
		return $this;
	}

	/**
     * 查询SQL组装 group
	 *
     * @access public 
     * @param string $group 
     * @return Model
     */
	function group( $group ){
		$this->_condition['group'] = $group;
		return $this;
	}
	
	/**
	 * 设置查询缓存
	 *
	 * @access public 
	 * @param bool $cache 
	 * @return Model
	 */
	function cache( $cache=true ){
		$this->_condition['cache'] = $cache;
		return $this;
	}
	
	/**
     * 清楚查询条件
	 *
     * @access public 
     * @return Model
     */
	function clearCondition(){
		$this->_condition = null;
		return $this;
	}

	/**
     * 启动事务
	 *
     * @access public 
     * @return void
     * @throws ThinkExecption
     */
	public function startTrans()
	{
		$this->commit();
		self::$_conn->startTrans();
		return;
	}

	/**
     * 提交事务
	 *
     * @access public 
     * @return boolean
     * @throws Execption
     */
	public function commit()
	{
		return self::$_conn->commit();
	}

	/**
     * 事务回滚
	 *
     * @access public 
     * @return boolean
     * @throws Execption
     */
	public function rollback()
	{
		return self::$_conn->rollback();
	}

	/**
     * 强制刷新数据表信息
	 *
	 * @access public 
     * @return void
     */
	public function flush() {
		$sql = 'SHOW COLUMNS FROM `'.$this->_trueTableName.'`';
		$result = self::$_conn->getAll( $sql );
		$info   =   array();
		$pks = array();
		foreach ($result as $key => $val) {
			if(is_object($val)) {
				$val	=	get_object_vars($val);
			}
			$info[] = $val['Field'];
			if( strtolower($val['Key']) == 'pri' ){
				$pks[] = $val['Field'];
			}
		}
		$this->_fields = $info;
		if( !empty( $pks ) ){
			$this->_tablePK = implode( ',',$pks );
			$info['_pk'] = $this->_tablePK;
		}
		// 永久缓存数据表信息
		if( conf( 'DB_TABLE_CACHE' ) ) {
			$cacheFile = conf( 'TEMP_PATH' ).'/~table.'.$this->_tableName.'.php';
			if( is_writeable( conf( 'TEMP_PATH' ) ) ){
				$content = 'return '.var_export( $info,true ).";\n";
				$content  = "<?php\n".$content."\n?>";
				file_put_contents( $cacheFile,$content );
			}
		}
	}
	
	/**
     * 对保存到数据库的数据对象进行处理
	 * 统一使用数组方式到数据库中间层 facade字段支持
     *
	 * @access protected 
     * @param mixed $data 要操作的数据
     * @return array
     */
	protected function facade( $data ){
		$reData = null;
		
		foreach( $this->_fields as $fields ){
			if( isset( $data[$fields] ) )
				$reData[$fields] = $data[$fields];
		}
		return $reData;
	}
	
	/**
	 * 数据表字段检测
	 *
	 * @access protected 
	 * @return void
	 */
	protected function getTableInfo(){
		if( !empty( $this->_fields ) || empty( $this->_tableName ) )
			return;
		//刷新table infos
		if( isset( $_GET['flushtableinfo'] ) ){
			$this->flush();
			return true;
		}
		$cache = conf( 'TEMP_PATH' ).'/~table.'.$this->_tableName.'.php';
		if( file_exists( $cache ) && conf( 'DB_TABLE_CACHE' ) ){
			$info = include $cache;
			if( isset( $info['_pk'] ) ){
				$this->_tablePK = $info['_pk'];
				unset( $info['_pk'] );
			}
			$this->_fields = $info;
		}else{
			$this->flush();
		}
	}

	/**
	 * 根据主键值获取where条件
	 *
	 * @access private
	 * @param mix $pk 主键值
	 * @return array or string
	 */
	private function getPkCondition( $pk ){
		if( is_string( $pk ) ){
			$pk = explode( ',',$pk );
			$pkFields = explode( ',',$this->_tablePK );
			foreach( $pkFields as $key=>$fields ){
				$wh[$fields] = $pk[$key];
			}
		}elseif( is_array( $pk ) ){
			$wh = $pk;
		}else{
			$wh[$this->_tablePK] = $pk; 
		}
		return $wh;
	}
	
	/**
     * 数据库Read操作入口
	 *
     * @access private 
	 * @param boolean $cache 是否查询缓存
     * @param mixed $where 查询条件
     * @param string $fields 查询字段
     * @param string $limit 
     * @param string $order 
     * @param string $join 
     * @param string $group 
     * @return array
     */
	private function read( $cache=false,$where='',$fields='*',$limit='',$order='',$join='',$group='' ){
		$table = $this->_trueTableName;
		if( !empty( $this->_condition) ){
			$table = !empty( $this->_condition['table'] ) ? $this->_condition['table'] : $table;
			
			$fields = !empty( $this->_condition['field'] ) ? $this->_condition['field'] : $fields;

			$where = !empty( $this->_condition['where'] ) ? $this->_condition['where'] : $where;

			$limit = !empty( $this->_condition['limit'] ) ? $this->_condition['limit'] : $limit;

			$order = !empty( $this->_condition['order'] ) ? $this->_condition['order'] : $order;

			$join = !empty( $this->_condition['join'] ) ? $this->_condition['join'] : $join;

			$group = !empty( $this->_condition['group'] ) ? $this->_condition['group'] : $group;

			$cache = !empty( $this->_condition['cache'] ) ? $this->_condition['cache'] : $cache;

			if( $this->_autoClear )
				$this->clearCondition();
		}
		//缓存数据
		if( $cache ){
			import("Cache");
			$identify   = $table.'List_'.Cache::getUiqMark(func_get_args());
			$rs  =  cache($identify);
			if( $rs!==false )
				return $rs;
		}
		self::$_conn->select( $table,$where,$fields,$limit,$order,$join,$group );
		if( $limit === 1)
			$rs = self::$_conn->getRow();
		else
			$rs = self::$_conn->getAll();
		//缓存数据
		if( $cache && $rs ){
			if( is_int( $cache ) )
				cache( $identify,$rs,$cache );
			else
				cache( $identify,$rs );
		}
		return $rs;
	}

	/**
     * 执行数据库操作更新数据
	 *
     * @access public 
     * @param mixed $data 要更新的数据
     * @param mixed $where 更新数据的条件
     * @param integer $limit 要更新的记录数
     * @param string $order  更新的顺序
     * @return boolen or int
     */
	private function doUpdate( $data,$where=null,$limit=null,$order=null ){
		$table = $this->_trueTableName;
		$fdata = $this->facade( $data );
		$data = !empty( $fdata ) ? $fdata : $data;

		if( !empty( $this->_condition) ){
			$table = !empty( $this->_condition['table'] ) ? $this->_condition['table'] : $table;

			$where = !empty( $this->_condition['where'] ) ? $this->_condition['where'] : $where;

			$limit = !empty( $this->_condition['limit'] ) ? $this->_condition['limit'] : $limit;

			$order = !empty( $this->_condition['order'] ) ? $this->_condition['order'] : $order;

			if( $this->_autoClear )
				$this->clearCondition();
		}
		if( empty( $data ) )
			return false;
		if( self::$_conn->update( $table,$data,$where,$limit,$order ) )
			return self::$_conn->affectedRows();
		return false;
	}

	/**
	 * 执行数据库删除数据
	 *
	 * @access public 
	 * @param string $where where条件语句
	 * @param integer $limit 要删除的记录数
     * @return boolen or int
	 */
	private function delete( $where,$limit ){
		$table = $this->_trueTableName;

		if( !empty( $this->_condition) ){
			$table = !empty( $this->_condition['table'] ) ? $this->_condition['table'] : $table;

			$where = !empty( $this->_condition['where'] ) ? $this->_condition['where'] : $where;

			$limit = !empty( $this->_condition['limit'] ) ? $this->_condition['limit'] : $limit;

			if( $this->_autoClear )
				$this->clearCondition();
		}
		if( self::$_conn->delete( $table,$where,$limit ) ){
			return self::$_conn->affectedRows();
		}
		return false;
	}
}
?>